#include "W25Qxx.h"



struct W25Qxx {
    enum {
        W25QXX_CMD_WRITE_ENABLE = 0x06,
        W25QXX_CMD_WRITE_DISABLE = 0x04,
        W25QXX_CMD_READ_STATUS_REG = 0x05,
        W25QXX_CMD_WRITE_STATUS_REG = 0x01,
        W25QXX_CMD_READ_DATA = 0x03,
        W25QXX_CMD_FAST_READ = 0x0B,
        W25QXX_CMD_FAST_READ_DUAL_OUT = 0x3B,
        W25QXX_CMD_PAGE_PROGRAM = 0x02,
        W25QXX_CMD_ERASE_SECTOR = 0x20,
        W25QXX_CMD_ERASE_BLOCK_32K = 0x52,
        W25QXX_CMD_ERASE_BLOCK_64K = 0xD8,
        W25QXX_CMD_ERASE_CHIP = 0xC7,
        W25QXX_CMD_POWER_DOWN = 0xB9,
        W25QXX_CMD_RELEASE_POWER_DOWN = 0xAB,
        W25QXX_CMD_DEVICE_ID = 0xAB,
        W25QXX_CMD_MANUFACTURER_ID = 0x90,
        W25QXX_CMD_JEDEC_ID = 0x9F,
    } W25QXX_CMD;


    uint16_t deviceID;
    uint8_t manufacturerID;
    uint8_t statusReg;
    uint16_t pageSize;
    uint16_t sectorSize;
    SPI_HandleTypeDef* spi;
    GPIO_TypeDef* csPort;
    uint16_t csPin;
};


/* 私有函数定义 */

/* 文件系统专用 */

void W25QxxWaitBusyInFileSystem( W25QxxTypeDef* w25qxx ) {
    while (W25QxxReadSRInFileSystem( w25qxx ) & 0x01);
}

static void W25QxxWriteEnableInFileSystem( W25QxxTypeDef* w25qxx ) {
    uint8_t command = W25QXX_CMD_WRITE_ENABLE;
    W25QxxCsEnable( w25qxx );
    SpiTransmitByte( w25qxx->spi, &command );
    W25QxxCsDisable( w25qxx );
}

static uint8_t W25QxxWritePageInFileSystem( W25QxxTypeDef* w25qxx, uint32_t addr, uint8_t* data, uint16_t len ) {
    if (len > w25qxx->pageSize || len == 0) {
        return W25QXX_PAGE_WRITE_ERROR;
    }

    W25QxxWriteEnableInFileSystem( w25qxx );
    W25QxxCsEnable( w25qxx );
    uint8_t txBuffer[] = { W25QXX_CMD_PAGE_PROGRAM, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF };
    SpiTransmit( w25qxx->spi, txBuffer, 4 );
    SpiTransmit( w25qxx->spi, data, len );
    W25QxxCsDisable( w25qxx );
    W25QxxWaitBusyInFileSystem( w25qxx );

    return W25QXX_OK;
}

static uint8_t W25QxxWriteNoCheckInFileSystem( W25QxxTypeDef* w25qxx, uint32_t addr, uint8_t* data, uint16_t len ) {
    uint16_t pageremain;
    //计算单页剩余的字节数
    pageremain = w25qxx->pageSize - addr % w25qxx->pageSize;

    if (len <= pageremain) {
        pageremain = len;
    }

    while (1) {
        W25QxxWritePageInFileSystem( w25qxx, addr, data, pageremain );
        if (len == pageremain) {
            break;
        } else {
            data += pageremain;
            addr += pageremain;
            len -= pageremain;
            if (len > w25qxx->pageSize) {
                pageremain = w25qxx->pageSize;
            } else {
                pageremain = len;
            }
        }
    }
    return W25QXX_OK;
}

uint16_t W25QxxReadIDInFileSystem( W25QxxTypeDef* w25qxx ) {

    W25QxxCsEnable( w25qxx );

    uint8_t txBuffer[] = { W25QXX_CMD_MANUFACTURER_ID, 0x00, 0x00 , 0x00 };
    uint8_t rxByte[2] = { 0 };
    SpiTransmit( w25qxx->spi, txBuffer, 4 );
    SpiReceive( w25qxx->spi, rxByte, 2 );
    w25qxx->deviceID = rxByte[0] << 8 | rxByte[1];
    W25QxxCsDisable( w25qxx );

    return w25qxx->deviceID;
}

uint8_t W25QxxReadSRInFileSystem( W25QxxTypeDef* w25qxx ) {
    uint8_t command = W25QXX_CMD_READ_STATUS_REG;
    uint8_t rxByte = 0x00;
    W25QxxCsEnable( w25qxx );
    SpiTransmitByte( w25qxx->spi, &command );
    SpiReceiveByte( w25qxx->spi, &rxByte );
    W25QxxCsDisable( w25qxx );
    w25qxx->statusReg = rxByte;
    return w25qxx->statusReg;
}

void W25QxxWriteSRInFileSystem( W25QxxTypeDef* w25qxx, uint8_t sr ) {
    uint8_t txBuffer[] = { W25QXX_CMD_WRITE_STATUS_REG, sr };
    W25QxxCsEnable( w25qxx );
    SpiTransmit( w25qxx->spi, txBuffer, 2 );
    W25QxxCsDisable( w25qxx );
}

static void W25QxxWriteDisableInFileSystem( W25QxxTypeDef* w25qxx ) {
    uint8_t command = W25QXX_CMD_WRITE_DISABLE;
    W25QxxCsEnable( w25qxx );
    SpiTransmitByte( w25qxx->spi, &command );
    W25QxxCsDisable( w25qxx );
}


static void W25QxxEraseSectorInFileSystem( W25QxxTypeDef* w25qxx, uint32_t addr ) {
    W25QxxWriteEnableInFileSystem( w25qxx );
    W25QxxWaitBusyInFileSystem( w25qxx );
    W25QxxCsEnable( w25qxx );
    uint8_t txBuffer[] = { W25QXX_CMD_ERASE_SECTOR, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF };
    SpiTransmit( w25qxx->spi, txBuffer, 4 );
    W25QxxCsDisable( w25qxx );
    W25QxxWaitBusyInFileSystem( w25qxx );
}



uint8_t W25QxxWriteInFileSystem( W25QxxTypeDef* w25qxx, uint32_t addr, uint8_t* data, int16_t len ) {
    uint32_t secpos;
    uint16_t secoff;
    uint16_t secremain;

    /* 扇区地址 0~2047 for w25q64*/
    secpos = addr / w25qxx->sectorSize;
    /* 判断是否超出容量 */
    if (secpos >= 2048 || secpos + len / w25qxx->sectorSize >= 2048) {
        return W25QXX_BEYOND_CAPACITY;
    }

    /* 在扇区内的偏移 */
    secoff = addr % w25qxx->sectorSize;

    /* 扇区剩余空间大小 */
    secremain = w25qxx->sectorSize - secoff;

    /* 若写入数据的长度小于等于扇区剩余空间的长度 */
    if (len <= secremain) {

        /* 写入前擦除扇区 */
        W25QxxEraseSectorInFileSystem( w25qxx, secpos * w25qxx->sectorSize );

        /* 写入数据 */
        W25QxxWriteNoCheckInFileSystem( w25qxx, addr, data, len );

        return W25QXX_OK;
    } else {

        do {

            /* 写入前擦除扇区 */
            W25QxxEraseSectorInFileSystem( w25qxx, secpos * w25qxx->sectorSize );

            /* 写入扇区剩余的数据 */
            W25QxxWriteNoCheckInFileSystem( w25qxx, addr, data, secremain );

            /* 计算剩余数据的长度 */
            len -= secremain;

            /* 计算下一个扇区的数据 */
            data += secremain;

            /* 计算下一个扇区的地址 */
            addr += secremain;

            /* 计算剩余数据的扇区地址 */
            secpos++;

            /* 计算下一个扇区的偏移 */
            secoff = 0;

            /* 下一个扇区的剩余空间为全空 */
            secremain = w25qxx->sectorSize;

        } while (len > 0);

    }

    return W25QXX_OK;
}

/* 通用函数 */

void W25QxxWaitBusy( W25QxxTypeDef* w25qxx ) {
    while (W25QxxReadSR( w25qxx ) & 0x01);
}





static uint8_t W25QxxWritePage( W25QxxTypeDef* w25qxx, uint32_t addr, uint8_t* data, uint16_t len ) {

    if (len > w25qxx->pageSize || len == 0) {
        return W25QXX_PAGE_WRITE_ERROR;
    }
    W25QxxWriteEnable( w25qxx );
    W25QxxCsEnable( w25qxx );
    uint8_t txBuffer[] = { W25QXX_CMD_PAGE_PROGRAM, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF };
    SpiTransmitDMA( w25qxx->spi, txBuffer, 4 );
    SpiTransmitDMA( w25qxx->spi, data, len );
    W25QxxCsDisable( w25qxx );
    W25QxxWaitBusy( w25qxx );

    return W25QXX_OK;
}



static uint8_t W25QxxWriteNoCheck( W25QxxTypeDef* w25qxx, uint32_t addr, uint8_t* data, uint16_t len ) {
    uint16_t pageremain;
    //计算单页剩余的字节数
    pageremain = w25qxx->pageSize - addr % w25qxx->pageSize;

    if (len <= pageremain) {
        pageremain = len;
    }

    while (1) {
        W25QxxWritePage( w25qxx, addr, data, pageremain );
        if (len == pageremain) {
            break;
        } else {
            data += pageremain;
            addr += pageremain;
            len -= pageremain;
            if (len > w25qxx->pageSize) {
                pageremain = w25qxx->pageSize;
            } else {
                pageremain = len;
            }
        }
    }
    return W25QXX_OK;
}


W25QxxTypeDef* W25Qxx( SPI_HandleTypeDef* spi, GPIO_TypeDef* csPort, uint16_t csPin ) {
    W25QxxTypeDef* w25qxx = (W25QxxTypeDef*) malloc( sizeof( W25QxxTypeDef ) );
    w25qxx->spi = spi;
    w25qxx->csPort = csPort;
    w25qxx->csPin = csPin;
    w25qxx->pageSize = 256;
    w25qxx->sectorSize = 4096;
    return w25qxx;
}

void W25QxxCsEnable( W25QxxTypeDef* w25qxx ) {
    HAL_GPIO_WritePin( w25qxx->csPort, w25qxx->csPin, GPIO_PIN_RESET );
}

void W25QxxCsDisable( W25QxxTypeDef* w25qxx ) {
    HAL_GPIO_WritePin( w25qxx->csPort, w25qxx->csPin, GPIO_PIN_SET );
}

uint16_t W25QxxReadID( W25QxxTypeDef* w25qxx ) {

    W25QxxCsEnable( w25qxx );

    uint8_t txBuffer[] = { W25QXX_CMD_MANUFACTURER_ID, 0x00, 0x00 , 0x00 };
    uint8_t rxByte[2] = { 0 };
    SpiTransmitDMA( w25qxx->spi, txBuffer, 4 );
    SpiReceiveDMA( w25qxx->spi, rxByte, 2 );
    w25qxx->deviceID = rxByte[0] << 8 | rxByte[1];

    W25QxxCsDisable( w25qxx );

    return w25qxx->deviceID;
}



uint8_t W25QxxReadSR( W25QxxTypeDef* w25qxx ) {
    uint8_t command = W25QXX_CMD_READ_STATUS_REG;
    uint8_t rxByte = 0x00;
    W25QxxCsEnable( w25qxx );
    SpiTransmitByteDMA( w25qxx->spi, &command );
    SpiReceiveByteDMA( w25qxx->spi, &rxByte );
    W25QxxCsDisable( w25qxx );
    w25qxx->statusReg = rxByte;
    return w25qxx->statusReg;
}



void W25QxxWriteSR( W25QxxTypeDef* w25qxx, uint8_t sr ) {
    uint8_t txBuffer[] = { W25QXX_CMD_WRITE_STATUS_REG, sr };
    W25QxxCsEnable( w25qxx );
    SpiTransmitDMA( w25qxx->spi, txBuffer, 2 );
    W25QxxCsDisable( w25qxx );
}



static void W25QxxWriteEnable( W25QxxTypeDef* w25qxx ) {
    uint8_t command = W25QXX_CMD_WRITE_ENABLE;
    W25QxxCsEnable( w25qxx );
    SpiTransmitByteDMA( w25qxx->spi, &command );
    W25QxxCsDisable( w25qxx );
}

static void W25QxxWriteDisable( W25QxxTypeDef* w25qxx ) {
    uint8_t command = W25QXX_CMD_WRITE_DISABLE;
    W25QxxCsEnable( w25qxx );
    SpiTransmitByteDMA( w25qxx->spi, &command );
    W25QxxCsDisable( w25qxx );
}


void W25QxxRead( W25QxxTypeDef* w25qxx, uint32_t addr, uint8_t* data, uint16_t len ) {
    uint8_t txBuffer[] = { W25QXX_CMD_READ_DATA, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF };
    W25QxxCsEnable( w25qxx );
    SpiTransmitDMA( w25qxx->spi, txBuffer, 4 );
    SpiReceiveDMA( w25qxx->spi, data, len );
    W25QxxCsDisable( w25qxx );
}

void W25QxxReadInFileSystem( W25QxxTypeDef* w25qxx, uint32_t addr, uint8_t* data, uint16_t len ) {
    uint8_t txBuffer[] = { W25QXX_CMD_READ_DATA, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF };
    W25QxxCsEnable( w25qxx );
    SpiTransmit( w25qxx->spi, txBuffer, 4 );
    SpiReceive( w25qxx->spi, data, len );
    W25QxxCsDisable( w25qxx );
}
/* W25Q64 存储大小为8M, 一共有2048扇区，每个扇区4KB*/
uint8_t W25QxxWrite( W25QxxTypeDef* w25qxx, uint32_t addr, uint8_t* data, int16_t len ) {
    uint32_t secpos;
    uint16_t secoff;
    uint16_t secremain;

    /* 扇区地址 0~2047 for w25q64*/
    secpos = addr / w25qxx->sectorSize;
    /* 判断是否超出容量 */
    if (secpos >= 2048 || secpos + len / w25qxx->sectorSize >= 2048) {
        return W25QXX_BEYOND_CAPACITY;
    }

    /* 在扇区内的偏移 */
    secoff = addr % w25qxx->sectorSize;

    /* 扇区剩余空间大小 */
    secremain = w25qxx->sectorSize - secoff;

    /* 若写入数据的长度小于等于扇区剩余空间的长度 */
    if (len <= secremain) {

        /* 写入前擦除扇区 */
        W25QxxEraseSector( w25qxx, secpos * w25qxx->sectorSize );

        /* 写入数据 */
        W25QxxWriteNoCheck( w25qxx, addr, data, len );

        return W25QXX_OK;
    } else {

        do {

            /* 写入前擦除扇区 */
            W25QxxEraseSector( w25qxx, secpos * w25qxx->sectorSize );

            /* 写入扇区剩余的数据 */
            W25QxxWriteNoCheck( w25qxx, addr, data, secremain );

            /* 计算剩余数据的长度 */
            len -= secremain;

            /* 计算下一个扇区的数据 */
            data += secremain;

            /* 计算下一个扇区的地址 */
            addr += secremain;

            /* 计算剩余数据的扇区地址 */
            secpos++;

            /* 计算下一个扇区的偏移 */
            secoff = 0;

            /* 下一个扇区的剩余空间为全空 */
            secremain = w25qxx->sectorSize;

        } while (len > 0);

    }

    return W25QXX_OK;
}


void W25QxxEraseChip( W25QxxTypeDef* w25qxx ) {
    W25QxxWriteEnable( w25qxx );
    W25QxxCsEnable( w25qxx );
    uint8_t command = W25QXX_CMD_ERASE_CHIP;
    SpiTransmitDMA( w25qxx->spi, &command, 1 );
    W25QxxCsDisable( w25qxx );
    W25QxxWaitBusy( w25qxx );
}

static void W25QxxEraseSector( W25QxxTypeDef* w25qxx, uint32_t addr ) {
    W25QxxWriteEnable( w25qxx );
    W25QxxWaitBusy( w25qxx );
    W25QxxCsEnable( w25qxx );
    uint8_t txBuffer[] = { W25QXX_CMD_ERASE_SECTOR, (addr >> 16) & 0xFF, (addr >> 8) & 0xFF, addr & 0xFF };
    SpiTransmitDMA( w25qxx->spi, txBuffer, 4 );
    W25QxxCsDisable( w25qxx );
    W25QxxWaitBusy( w25qxx );
}

void W25QxxPowerDown( W25QxxTypeDef* w25qxx ) {
    W25QxxCsEnable( w25qxx );
    uint8_t command = W25QXX_CMD_POWER_DOWN;
    SpiTransmitByteDMA( w25qxx->spi, &command );
    W25QxxCsDisable( w25qxx );
}

void W25QxxWakeUp( W25QxxTypeDef* w25qxx ) {
    W25QxxCsEnable( w25qxx );
    uint8_t command = W25QXX_CMD_RELEASE_POWER_DOWN;
    SpiTransmitByteDMA( w25qxx->spi, &command );
    W25QxxCsDisable( w25qxx );
}
